package rsa.rsa1;

//RSA算法的Java实现

import java.io.*;

public class RSA {
    //定义并初始化p、q、n、e、d等值
    private int p = 0;
    private int q = 0;
    private long n = 0;
    private long m = 0;
    private long public_key = 0;

    private long private_key = 0;//密钥

    private long text = 0;//明文
    private long secretWord = 0;//密文
    private long word = 0;//解密后明文

    //判断输入的p、q是否为primenumber（素数）
    public boolean isPrime(long t) {
        long k = 0;
        k = (long) Math.sqrt((double) t);
        boolean flag = true;
        outer:
        for (int i = 2; i <= k; i++) {
            if ((t % i) == 0) {
                flag = false;
                break outer;
            }
        }
        return flag;
    }

    //通过键盘输入素数p、q
    //判定输入是否是素数
    public void inputPQ() throws Exception {
        do {
            System.out.println("请输入素数p: ");
            BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
            String br = stdin.readLine();
            this.p = Integer.parseInt(br);
        }
        //如果输入不是素数，屏幕一直打出“请输入素数q”，直到输入是素数为止
        while (!isPrime(this.p));
        do {
            System.out.println("请输入素数q: ");
            BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
            String br = stdin.readLine();
            this.q = Integer.parseInt(br);
        }
        //如两个输入均符合条件（素数），计算它们的乘积
        while (!isPrime(this.q));
        this.n = this.p * this.q;
        this.m = (p - 1) * (q - 1);
        System.out.println("这两个素数的乘积n为：" + this.n);
        System.out.println("n的欧拉数=(p-1)(q-1)：" + this.m);
    }

    //求最大公因子，调用java的gcd函数，该函数返回的是两个或多个正数的最大公因子
    public long gcd(long a, long b) {
        long gcd;
        if (b == 0)
            gcd = a;
        else
            gcd = gcd(b, a % b);
        System.out.println("最大公因子:" + gcd);
        return gcd;

    }

    //提供一个公匙，这个值要求小于m并且和m互质这个值要求小于m并且和m互质
    public void getPublic_key() throws Exception {
        do {
            System.out.println("请输入公钥： ");
            BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
            String br = stdin.readLine();
            //用parseLong解析字符串，使其返回为Long型数据
            this.public_key = Long.parseLong(br);
        } while ((this.public_key >= this.m) || (this.gcd(this.m, this.public_key) != 1));
        System.out.println("公钥为：" + this.public_key);
    }

    //由欧几里得扩展法，计算密钥
    public void getPrivate_key() {
        long value = 1;
        outer:
        for (long i = 1; ; i++) {
            value = i * this.m + 1;
            System.out.println("value:  " + value);
            if ((value % this.public_key == 0) && (value / this.public_key < this.m)) {
                this.private_key = value / this.public_key;
                break outer;
            }
        }
        System.out.println("生成的私钥为：" + this.private_key);
    }


    //外部提供所需加密的明文
    public void getText() throws Exception {
        System.out.println("请输入明文：");
        //调用BufferReader从输入的字符流中读取文本，用于显示输出
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        String br = stdin.readLine();
        this.text = Long.parseLong(br);
    }

    //加密、解密计算
    //强制类型转换会出现警告，加上SuppressWarnings语句屏蔽警告信息
    @SuppressWarnings("empty-statement")
    public long colum(long y, long n, long key) {
        long mul;
        if (key == 1)
            mul = y % n;
        else
            mul = y * this.colum(y, n, key - 1) % n;
        return mul;
    }

    //先加密后解密
    public void pascolum() throws Exception {
        this.getText();
        System.out.println("输入明文为: " + this.text);
        //加密过程
        this.secretWord = this.colum(this.text, this.n, this.public_key);
        System.out.println("算出的密文为：" + this.secretWord);
        //解密过程
        this.word = this.colum(this.secretWord, this.n, this.private_key);
        System.out.println("解密后所得的明文为：" + this.word);

    }

    //java语言中，一个方法出现异常，必须将异常传递给调用方法，由其处理
    //声明抛出异常的语句是throws Exception
    public static void main(String[] args) throws Exception {
        RSA rsa = new RSA();
        rsa.inputPQ();//输入PQ
        rsa.getPublic_key();//获取公钥
        rsa.getPrivate_key();//获取私钥
        rsa.pascolum();//进行加解密
    }
}